# coding: utf8


import numpy as np
import pandas as pd
import random


class SeriesDemo:

    def __init__(self):
        self.sr1 = pd.Series(range(5),
                             name='series1')                        # 使用可迭代对象生成Series, 缺省索引RangeIndex

        self.sr2 = pd.Series(data=range(5),
                             dtype=np.int8,
                             name='series2')		                # 使用Numpy数据类型int8

        self.sr3 = self.sr2.copy(deep=True)		                    # 使用深度拷贝,产生独立的另一个Series数据
        self.sr3.index = [str(i) for i in range(5)]  	            # 使用字符串值作为索引
        self.sr3.name = 'series3'

        self.sr4 = self.sr3.copy(deep=True)
        self.sr4.name = 'series4'
        self.sr4.index = [1, 2, 1, 2, 3]		                    # 可以使用有重复值的序列作为索引

        self.sr5 = pd.Series(range(5),
                             index=[[0, 0, 1, 1, 1],                # 使用(整数、字符串)作为标签
                                    ['a', 'b', 'a', 'b', 'c']],
                             dtype=np.int8,
                             name='series5')

    def series_from_range(self):
        print('使用可迭代对象生成Series, 缺省索引RangeIndex')
        print('sr1 = pd.Series(range(5))')
        print(self.sr1)

    def series_dtype_int8(self):
        print('使用Numpy数据类型int8')
        print('sr2 = pd.Series(range(5), dtype=np.int8)')
        print(self.sr2)

    def series_copy(self):
        print('使用深度拷贝,产生独立的另一个Series数据')
        print('sr3 = sr2.copy(deep=True)')
        print(self.sr3)

    def series_set_index(self):
        print('可以使用有重复值的序列作为索引')
        sr = self.sr3
        print('srcopy = sr.copy(deep=True)')
        srcopy = self.sr3.copy(deep=True)
        print(srcopy)
        print('sr4.index = [1, 2, 1, 2, 3]')
        print(self.sr4)

    def series_multi_index(self):
        print('使用(整数、字符串)作为标签, 建立多重索引')
        print("sr5 = pd.Series(range(5),\n\
               index=[[0, 0, 1, 1, 1], \n\
               ['a', 'b', 'a', 'b', 'c']],\n\
               dtype=np.int8)")
        print(self.sr5)

    def series_reindex(self):
        sr = pd.Series({chr(ord('a') + i): i + 1 for i in range(3)}, dtype=np.uint8)
        print(sr)

        # reindex fill data of new label with NaN
        sr1 = sr.reindex(index=['a', 'c', 'd'])
        print(sr1)

        # reindex fill data of new label with fill_value
        sr2 = sr.reindex(index=['a', 'c', 'd'], fill_value=100)
        print(sr2)

        # return copy data if index=old index and copy=False
        srnew = sr.reindex(index=['a', 'b', 'c'], copy=False)
        print(srnew is sr)

        # match multi-index at level
        print(self.sr5)
        # -- match all levels, add data if label not in source labels
        print("sr.reindex(index = [(0, 'c')], fill_value=9)")
        print(self.sr5.reindex(index=[(0, 'c')], fill_value=9))
        # -- match level-0, return empty Series if no lable match
        print("sr.reindex(index = [0, 3], level=0)")
        print(self.sr5.reindex(index = [0, 3], level=0))
        # -- match level-1, return empty Series if no lable match
        print("sr.reindex(index = ['e', 'c'], level=1)")
        print(self.sr5.reindex(index = ['e', 'c'], level=1))


class Demo:

    def __init__(self):
        self.df_list = pd.DataFrame(
            data=[[(x + 1) ** y for y in range(3)] for x in range(3)],
            columns=list('ABC'),
            index=pd.date_range('2010.1.1', periods=3),
            dtype=np.int8)

        self.df_dict = pd.DataFrame(
            data={'class': [2010, 2011, 2012],
                  'math': [75, 80, 90],
                  'science': [90, 65, 89]},
            index=pd.interval_range(1, 13, periods=3, closed='both')
            )

        # 数据集运算
        self.data = [[-1.037109, 0.784668, -0.408447],
                     [0.874512, 1.262695, -0.703613],
                     [-1.017578, 0.775391,  0.502441]]
        self.dfa = pd.DataFrame(
            data=np.array(self.data),
            index=pd.date_range('2010.1.1', '2010.1.3'),
            columns=['A', 'B', 'C']
            )
        self.dfb = self.dfa.copy(deep=True)
        self.dfb.index = pd.date_range('2010.1.2', periods=3)

        # MultiIndex-1
        self.df_mindex1 = pd.DataFrame(
            {'语文': [random.randint(60, 100) for _ in range(12)],
             '数学': [random.randint(60, 100) for _ in range(12)]},
            index=pd.MultiIndex.from_product(
                [['李明', '张乐', '何婷'], ['初考', '作业', '例考', '终考']]),
            dtype=np.uint8
            )

        # MultiIndex - 2
        self.df_mindex2 = pd.DataFrame(
            data=np.arange(60, 84).reshape(12, 2),
            columns=['math', 'science'],
            index=pd.MultiIndex.from_product(
                [[1, 2, 3], ['t1', 't2', 't3', 't4']]),
            dtype=np.uint8
            )

    def data_opera(self):
        dfa = pd.DataFrame(
            data={'A': [1, 2, 3], 'B': [10, 20, 30]},
            index=range(1, 4)
        )
        dfb = pd.DataFrame(
            data={'B': [100, 200, 300], 'C': [1000, 2000, 3000]},
            index=range(2, 5)
        )
        sr = pd.Series([-1, -2, -3, -4], index=['A', 'B', 'C', 'D'])

        print(
            f"{'-' * 80}\n"
            f"# DataFrame数据集之间运算\n"
            ' >>> dfa\n'
            f"{dfa}\n"

            ' >>> dfb\n'
            f"{dfb}\n"

            f' >>> dfa - dfb\n'
            f"{dfa - dfb}\n"

            f' >>> dfa.add(dfb, fill_value=100)\n'
            f"{dfa.add(dfb, fill_value=100)}\n"

            f"{'-' * 80}\n"
            f"# DataFrame与Series之间运算\n"
            f" >>> sr\n"
            f"{sr}\n"

            f"# 使用运算符直接运算，按照DataFrame的列Columns与Series的索引index对齐\n"
            f" >>> dfa + sr\n"
            f"{dfa + sr}\n"

            f"# 使用方法代替运算符进行运算，运算结果直接改变数据集，并且可以选择轴向\n"
            f" >>> dfa.add(sr, axis=1)\n"
            f"{dfa.add(sr, axis=1)}\n"
            f" >>> dfa.add(sr, axis=0)\n"
            f"{dfa.add(sr, axis=0)}\n"

            f" >>> dfa * sr\n"
            f"{dfa * sr}\n"

            f" >>> dfa.mul(sr)\n"
            f"{dfa.mul(sr)}\n"

            f" >>> dfa.div(sr)\n"
            f"{dfa.div(sr)}\n"
        )
        return dfa

    def df_op_series(self):
        df = self.df1
        sr = df[df.columns[0]]

        print('-'*80)
        print('DataFrame: df')
        print(df)
        print('\nSeries: sr1')
        print(sr)

        # add at axis=1
        print('-'*80)
        print("df + sr   # align Series label to DataFrame at axis=1")
        print(df+sr)

        # add at axis=1
        print('-'*80)
        df = self.dfa
        print('df')
        print(df)

        sr2 = pd.Series([200]*3, index=list('ABC'))
        print('\nsr2')
        print(sr2)
        print('\ndf.add(sr2, axis=1)   # align label at axis=1')
        print(df.add(sr2, axis=1))

        # ValueError: Length of passed values is 3, index implies 2
        print('-'*80)
        try:
            sr2 = pd.Series([200]*2, index=list('AB'))
            print('sr2 with less columns:')
            print(sr2)
            print('\ndf.add(sr2, axis=1)   # align label at axis=1  columns length is not equal')
            print(df.add(sr2, axis=1))
        except ValueError as e:
            print("ValueError:", e)

        # add at axis=0
        print('-'*80)
        sr3 = pd.Series([300]*3, index=pd.date_range('2010.1.2', '2010.1.4'))
        print('sr3')
        print(sr3)
        print("\ndf.add(sr3, axis=0): align Series label at axis=0")
        print(df.add(sr3, axis=0))

        # NotImplementedError: fill_value 0 not supported.
        print('-'*80)
        try:
            print("df.add(sr3, axis=0, fill_value=0)")
            print(df.add(sr3, axis=0, fill_value=0))
        except NotImplementedError as e:
            print('NotImplementedError: ', e)

        print('-'*80)
        print("df.add(sr3, axis=0).fillna(100)")
        print(df.add(sr3, axis=0).fillna(100))

    def df_op_value(self):
        df = self.dfa
        df['B'] = 'abc'
        df['C'] = 123
        print('df')
        print(df)
        print(df.info())

        print("df + 10: TypeError")
        try:
            print(df + 10)
        except TypeError as e:
            print('TypeError:', e)

        print("df['B']+'10'")
        df['B'] = df['B']+'10'
        print(df)

        print("sr = pd.Series([1, 2, 3], index=pd.date_range('2010.1.1', periods=3))")
        print("df['C'] = df['C'] + sr")
        sr = pd.Series([1j, 2j, 3j], index=pd.date_range('2010.1.1', periods=3))
        df['C'] = df['C'] + sr
        print(df)

    def test_df_from_list(self):
        """
        >>> data0 = [[(x + 1) ** y for y in range(3)] for x in range(3)]
        >>> index0 = pd.date_range('2010.1.1', periods=3)
        >>> columns0 = ['a', 'b', 'c']
        >>> df = pd.DataFrame(data=data0, columns=columns0, index=index0, dtype=np.int8)
        >>> df
                    a  b  c
        2010-01-01  1  1  1
        2010-01-02  1  2  4
        2010-01-03  1  3  9
        """
        return
